package com.lap.framework.junit.application.builder;

import com.lap.framework.junit.domain.valueobject.JField;
import com.lap.framework.junit.domain.valueobject.JMethod;
import com.lap.framework.junit.shared.tool.AnnotationUtil;
import com.lap.framework.junit.shared.tool.TypeUtil;
import com.lap.framework.junit.shared.tool.UriUtil;
import io.swagger.v3.oas.annotations.Operation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

@Slf4j
public class MethodParser {

  public List<JMethod> parseMethods(Class<?> clazz, String parentUri) {
    return Arrays.stream(clazz.getMethods())
        .sorted(Comparator.comparing(Method::getName).reversed())
        .map(method -> resolveMethod(method, parentUri))
        .filter(Objects::nonNull)
        .collect(Collectors.toList());
  }

  private JMethod resolveMethod(Method method, String parentUri) {
    Pair<String, String> methodInfo = AnnotationUtil.getHttpMethodAndUri(method);
    if (StringUtils.isBlank(methodInfo.getRight())) {
      return null;
    }

    JMethod jMethod = new JMethod();
    jMethod.setUri(UriUtil.getUri(parentUri, methodInfo.getLeft()));
    jMethod.setMethod(methodInfo.getRight());
    jMethod.setCsvPath(jMethod.getUri());
    jMethod.setName(method.getName());
    jMethod.setRemark(resolveMethodRemark(method));
    jMethod.setFields(resolveParameters(method));
    jMethod.setPage(isPageMethod(method));
    jMethod.setArgMap(isArgMap(jMethod));
    jMethod.setPathVariable(AnnotationUtil.hasPathVariable(methodInfo.getLeft()));

    return jMethod;
  }

  private String resolveMethodRemark(Method method) {
    return Optional.ofNullable(method.getAnnotation(Operation.class))
        .map(Operation::summary)
        .orElse("");
  }

  private List<JField> resolveParameters(Method method) {
    return Arrays.stream(method.getParameters())
        .map(this::resolveParameter)
        .flatMap(List::stream)
        .collect(Collectors.toList());
  }

  private List<JField> resolveParameter(Parameter parameter) {
    if (TypeUtil.isPrimitiveOrWrapperOrString(parameter.getType())) {
      return Collections.singletonList(new JField(parameter.getType(), parameter.getName()));
    } else {
      return Arrays.stream(parameter.getType().getDeclaredFields())
          .map(field -> new JField(field.getType(), field.getName()))
          .collect(Collectors.toList());
    }
  }

  private boolean isPageMethod(Method method) {
    return Arrays.stream(method.getParameters())
        .anyMatch(
            parameter ->
                Optional.ofNullable(parameter.getType().getSuperclass())
                    .map(Class::getSimpleName)
                    .filter("PageRequest"::equalsIgnoreCase)
                    .isPresent());
  }

  private boolean isArgMap(JMethod method) {
    return method.getFields().size() > 3;
  }
}
